/**
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
using System;
using System.IO;
using System.Collections;
using Dokan;
using QDrive.Domain;
using QDrive;
using QDrive.QBClient;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace QDrive
{
    class QDrive : DokanOperations
    {
        private int count_;
        private DirectoryInfo _root;

        private Boolean _debug = false;

        // Every 2 minutes update a copy from QuickBase
        private const int DOWNLOAD_REFRESH_INTERVAL = 60;
        private const int APPLICATION_UPDATE_FREQ = 20;

        private const string QB_FOLDER = "QuickBase";
        private const string QB_ROOT = "QuickBase Applications";
        private const string QB_TABLES = "Tables";
        private const string QB_TABLE_REPORTS = "Reports";
        private const string QB_TABLE_DATA = "Data";
        private const string QB_DASHBOARD = "Dashboard.html";
        private const string QB_FILE_ATTACHMENTS = "File Attachments";
        private const string QB_OPEN_QUICKBASE = "Open Quickbase.html";
        private const string QB_CONFIGURE_APP_TOKEN = "Configure application tokens to view.html";

        private const string QB_DROP_TO_CREATE_APPLICATION = "Drop CSV or Excel Document and Create Application";
        private DateTime startTime = DateTime.Now;
		private Object thisLock = new Object();

        private Dictionary<String, List<DBID>> appToTableCache = new Dictionary<String, List<DBID>>();
        private Dictionary<String, List<Report>> tableToReports = new Dictionary<String, List<Report>>();
        private Dictionary<String, Report> reportToPath = new Dictionary<String, Report>();

        private Dictionary<String, List<QBField>> tableFields = new Dictionary<string, List<QBField>>();

        private HashSet<String> filesToUpload = new HashSet<String>();

        // Captured so that we can pull the record identifier off when a get request is made.
        private Dictionary<String, List<FileAttachment>> fileAttachmentsByTable = new Dictionary<string, List<FileAttachment>>();
        private DateTime lastUpdatedApplications;
        private IEnumerable applications;
        private QBAPI api = null;

        public QDrive(QuickBaseClient client)
        {
            count_ = 1;
            _root = CreateAndGetHomeDir();			
			// Authenticate with QB first...
            api = new QBAPI(client);
        }

        public int CreateFile(String filename, FileAccess access, FileShare share,
            FileMode mode, FileOptions options, DokanFileInfo info)
        {
            info.Context = count_++;
            if (filename.Contains("\\" + QB_FILE_ATTACHMENTS + "\\") && filename.LastIndexOf('.') > 0)
            {
                if (FileAttachmentExists(filename)) 
                    return 0;
                return 0;
            }
            else if ((filename.EndsWith(".csv") || filename.EndsWith(".html")) && (filename.StartsWith("\\" + QB_ROOT) || filename.Equals("\\" + QB_OPEN_QUICKBASE)))
            {
                return 0;
            }
            else if (filename.StartsWith("\\" + QB_ROOT) || filename.Equals("\\"))
            {
                info.IsDirectory = true;
                return 0;
            }
            return -DokanNet.ERROR_FILE_NOT_FOUND;
        }

        public int OpenDirectory(String filename, DokanFileInfo info)
        {
            info.Context = count_++;
            if (filename.StartsWith("\\" + QB_ROOT) || filename.Equals("\\")) 
                return 0;

            return -DokanNet.ERROR_PATH_NOT_FOUND;
        }

        public int CreateDirectory(String filename, DokanFileInfo info)
        {
            //Console.WriteLine("Create Directory: {0}", filename);
            return -1;
        }

        public int Cleanup(String filename, DokanFileInfo info)
        {
            //Console.WriteLine("Cleanup: {0}", filename);
            return 0;
        }

        public int CloseFile(String filename, DokanFileInfo info)
        {
            lock (thisLock)
            {
                if (filesToUpload.Contains(filename) && GetFileAttachmentByPath(filename) == null)
                {
                    DBID tableId = getTableIdByPath(filename);
                    List<QBField> fields = GetFieldsWithFileAttachment(tableId.id);
                    QBField field = fields[0];

                    int pos = filename.LastIndexOf('\\') + 1;
                    String suffix = filename.Substring(pos, filename.Length - pos);
                    byte[] buffer = GetCloneFileContents(filename);
                    api.UploadFile(tableId, fields[0], buffer, suffix);
                    // refresh the file list
                    GetFileAttachments(tableId.id);
                    filesToUpload.Remove(filename);
                }
            }

            //Console.WriteLine("CloseFile: {0}", filename);
            return 0;
        }

        private byte[] GetCloneFileContents(string filename)
        {
            MemoryStream ms = new MemoryStream();
            FileStream fs = File.OpenRead(_root + filename);
            byte[] buffer = new byte[8096];    
            int readBytes = 0;
            ms.Seek(0, SeekOrigin.Begin);
            do
            {
                readBytes = fs.Read(buffer, 0, buffer.Length);
                ms.Write(buffer, 0, readBytes);
            }
            while (readBytes > 0);
            fs.Close();

            if (_debug) Console.WriteLine("GetCloneFileContents: {0}, file: {1}", ms.Length, filename);
            byte []outBuffer = new byte[ms.Length];
            ms.Seek(0, SeekOrigin.Begin);
            ms.Read(outBuffer, 0, outBuffer.Length);
            ms.Close();
            return outBuffer;
        }

        private Report getReportByPath(string filename)
        {
            string prefix = filename.Substring(0, filename.LastIndexOf('.')+1);
            prefix = prefix.Replace("\\Data\\", "\\");
            prefix = prefix.Replace("\\Reports\\", "\\");
            if (reportToPath.ContainsKey(prefix))
            {
                return reportToPath[prefix];
            }
            return null;
        }

        private String GetPathPart(string filename, int index)
        {
            String[] parts = filename.Split('\\');
            if (parts.Length >= index)
            {
                return parts[index];
            }
            return null;
        }

        private DBID getAppIdByPath(string filename)
        {
            string appName = GetPathPart(filename, 2);
            foreach (DBID appId in GetApplications())
            {
                if (appName.Equals(this.MakeValidFileName(appId.label)))
                    return appId;
            }
            return null;
        }

        private DBID getTableIdByPath(string filename)
        {
            DBID appId = getAppIdByPath(filename);
            if (null != appId) {
                string tableName = GetPathPart(filename, 3);
                foreach (DBID tableId in GetTables(appId.id))
                {
                    if (tableName.Equals(this.MakeValidFileName(tableId.label)))
                        return tableId;
                }
            }
            return null;
        }

        public int ReadFile(String filename, Byte[] buffer, ref uint readBytes,
            long offset, DokanFileInfo info)
        {
            
            lock (thisLock)
            {
                if (_debug) Console.WriteLine("ReadFile: {0}", filename);
                if (filename.StartsWith("\\" + QB_ROOT) && filename.EndsWith(".csv") && !filename.Contains("\\" + QB_FILE_ATTACHMENTS + "\\"))
                {
                    Report report = getReportByPath(filename);
                    if (null != report)
                    {
                        // If the CSV content is not been retried before, or alternatively if its been 30 seconds since we last downloaded it pull it again
                        //TimeSpan diff = (null != report && report.lastUpdate != null) ? (DateTime.Now.Subtract(report.lastUpdate)) : new TimeSpan(61);
                        // && diff.Seconds > 60
                        if (report.csvLocalFile == null ) {
                            byte[] csvContent = System.Text.Encoding.ASCII.GetBytes(api.DownloadCSV(report));
                            report.csvSize = csvContent.Length;
                            report.lastUpdate = DateTime.Now;
                            report.csvLocalFile = InternalSaveCopy(filename, csvContent, csvContent.Length);
                            csvContent = null;
                            if (_debug) Console.WriteLine("Downloaded report QID: {0}, TableID: ", report.qid, report.tableId.id);
                        }

                        int rc = InternalReadFile(report.csvLocalFile, offset, buffer, ref readBytes);
                        if (_debug) Console.WriteLine("ReadFile QID: {0}, Offset = {1}, Bytes read: {2}", report.qid, offset, readBytes);
                        return 0;
                    }
                }
                else if (filename.StartsWith("\\") && filename.EndsWith(".html") && !filename.Contains("\\" + QB_FILE_ATTACHMENTS + "\\"))
                {
                    String content = null;
                    Report report = getReportByPath(filename);
                    if (null != report)
                    {
                        this.api.client.SelectDbid(report.tableId.id);
                        content = "<html><head><meta http-equiv=refresh CONTENT='0;" + this.api.client._uri + "?a=q&qid=" + report.qid + "&ticket=" + this.api.client._ticket + "'></head></html>";
                    }
                    else if (filename.EndsWith(QB_DASHBOARD))
                    {
                        // Get the app identifier from the path
                        DBID appId = getAppIdByPath(filename);
                        if (null != appId)
                        {
                            content = "<html><head><meta http-equiv=refresh CONTENT='0;" + this.api.client._uri + "?ticket=" + this.api.client._ticket + "'></head></html>";
                        }
                    }
                    else if (filename.EndsWith(QB_CONFIGURE_APP_TOKEN))
                    {
                        DBID appId = getAppIdByPath(filename);
                        if (null != appId)
                        {
                            this.api.client.SelectDbid(appId.id);
                            content = "<html><head><meta http-equiv=refresh CONTENT='0;" + this.api.client._uri + "?a=AppManage&ticket=" + this.api.client._ticket + "'></head></html>";
                        }
                    }
                    else if (filename.EndsWith(QB_OPEN_QUICKBASE))
                    {
                        content = "<html><head><meta http-equiv=refresh CONTENT='0;https://" + this.api.client._domain + "/db/main?a=myqb&ticket=" + this.api.client._ticket + "'></head></html>";
                    }

                    if (null != content)
                    {
                        byte[] buf = System.Text.Encoding.ASCII.GetBytes(content);
                        if (offset > buf.Length)
                        {
                            readBytes = 0;
                            return 0;
                        }
                        uint br = 0;
                        long minValue = (buffer.Length > buf.Length) ? buf.Length : buffer.Length;
                        for (long i = offset; i < minValue; i++)
                        {
                            buffer[br++] = buf[i];
                        }
                        if (_debug) Console.WriteLine("HTML Content: {0}, Total: {1}, Buffer: {2}", br, content.Length, buffer.Length);
                        readBytes = br;
                        return 0;
                    }
                }
                else if (filename.Contains("\\" + QB_FILE_ATTACHMENTS + "\\"))
                {
                    DBID tableId = getTableIdByPath(filename);
                    if (null != tableId)
                    {
                        if (fileAttachmentsByTable.ContainsKey(tableId.id))
                        {
                            FileAttachment attachment = GetFileAttachmentByPath(filename);
                            if (attachment != null)
                            {
                                TimeSpan diff = (attachment.lastUpdated != null) ? (DateTime.Now.Subtract(attachment.lastUpdated)) : new TimeSpan(61);
                                if (attachment.file == null || diff.Seconds > 60)
                                {
                                    byte[] content = api.GetFile(attachment);
                                    if (_debug) Console.WriteLine("Downloaded file: {0}, size: {1}", filename, content.Length);
                                    attachment.file = InternalSaveCopy(filename, content, content.Length);
                                    attachment.lastUpdated = DateTime.Now;
                                    attachment.lastAccessTime = DateTime.Now;
                                }

                                int rc = InternalReadFile(attachment.file, offset, buffer, ref readBytes);
                                if (_debug) Console.WriteLine("ReadFile Attachment: Name: {0}, Offset = {1}, Bytes read: {2}", attachment.name, offset, readBytes);
                                return rc;
                            }
                        }
                    }

                    readBytes = 0;
                    return 0;
                }
                return -DokanNet.ERROR_FILE_NOT_FOUND;
            }
        }

        private int InternalReadFile(FileInfo fileInfo, long offset, byte[] buffer, ref uint readBytes)
        {
            try
            {
                FileStream fs = File.OpenRead(fileInfo.FullName);
                if (offset != 0) fs.Seek(offset, SeekOrigin.Begin);
                readBytes = (uint)fs.Read(buffer, 0, buffer.Length);
                fs.Close();
            }
            catch (Exception)
            {
                return -1;
            }
            return 0;
        }

        private FileInfo InternalWriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset)
        {
            lock (thisLock)
            {
                FileInfo fi = new FileInfo(_root + filename);
                string path = fi.Directory.FullName;

                if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                if (IsNullBuffer(buffer)) 
                    return fi;

                try
                {
                    FileMode mode = FileMode.Append;
                    if (offset == 0) mode = FileMode.CreateNew;
                    using (FileStream stream = new FileStream(fi.FullName, mode))
                    {
                        using (BinaryWriter writer = new BinaryWriter(stream))
                        {
                            writer.Write(buffer);
                            writer.Close();
                        }
                    }
                }
                catch (Exception) { }
                return fi;
            }
        }

        private FileInfo InternalSaveCopy(string filename, byte[] content, long length)
        {
            lock (thisLock)
            {
                FileInfo fi = new FileInfo(_root + filename);
                string path = fi.Directory.FullName;
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                try
                {
                    using (FileStream stream = new FileStream(fi.FullName, FileMode.Create))
                    {
                        using (BinaryWriter writer = new BinaryWriter(stream))
                        {
                            for (int i = 0; i < length; i++) {
                                writer.Write(content[i]);
                            }
                            writer.Close();
                        }
                    }
                }
                catch (Exception) { }
                return fi;
            }
        }

        public int WriteFile(String filename, Byte[] buffer,
            ref uint writtenBytes, long offset, DokanFileInfo info)
        {
            lock (thisLock)
            {
                if (_debug) Console.WriteLine("WriteFile: offset: {0}, length: {1}, file: {2}", offset, buffer.Length, filename);
                try
                {
                    DBID tableId = getTableIdByPath(filename);
                    if (null == tableId) return -1;

                    List<QBField> fields = GetFieldsWithFileAttachment(tableId.id);
                    if (filename.Contains("\\" + QB_FILE_ATTACHMENTS + "\\") && fields.Count != 0)
                    {
                        InternalWriteFile(filename, buffer, ref writtenBytes, offset);

                        if (!filesToUpload.Contains(filename)) filesToUpload.Add(filename);
                        return DokanNet.DOKAN_SUCCESS;
                    }
                }
                catch (Exception) { }
            }
            return -1;
        }

        private bool IsNullBuffer(byte[] buffer)
        {
            for (int i = 0; i < buffer.Length; i++)
            {
                if (buffer[i] != 0) return false;
            }
            return true;
        }

        public int FlushFileBuffers(String filename, DokanFileInfo info)
        {
            //Console.WriteLine("FlushFileBuffers: {0}", filename);
            return -1;
        }

        public int GetFileInformation(String filename, FileInformation fileinfo, DokanFileInfo info)
        {
            //Console.WriteLine("GetFileInformation: {0}", filename);
            lock (thisLock)
            {
                if (filename.StartsWith("\\"))
                {
                    fileinfo.CreationTime = DateTime.Now;
                    fileinfo.LastAccessTime = DateTime.Now;
                    fileinfo.LastWriteTime = DateTime.Now;
                    fileinfo.Attributes = FileAttributes.Archive | FileAttributes.ReadOnly;
                    if (filename.Contains("\\" + QB_FILE_ATTACHMENTS + "\\"))
                    {
                        FileAttachment attachment = GetFileAttachmentByPath(filename);
                        if (null != attachment && 0 != attachment.size) fileinfo.Length = attachment.size;

                        if (filename.EndsWith("\\folder.jpg") || filename.EndsWith(".svn") || filename.EndsWith(".git")) 
                            return -1;

                        return DokanNet.DOKAN_SUCCESS;
                    }
                    else if (filename.EndsWith(".csv") || filename.EndsWith(".html"))
                    {
                        Report report = getReportByPath(filename);
                        if (null != report)
                            fileinfo.Length = report.csvSize;
                        fileinfo.Attributes = FileAttributes.Archive | FileAttributes.ReadOnly;
                    }
                    else
                    {
                        fileinfo.Attributes = FileAttributes.Directory;
                        fileinfo.Length = 0;
                    }
                    return 0;
                }
            }
            return DokanNet.DOKAN_SUCCESS;
        }

        private String chompDir(string filename, int count)
        {
            String prefix = filename;
            for (int i = 0; i < count; i++)
            {
                prefix = prefix.Substring(0, prefix.LastIndexOf('\\'));
            }
            return prefix;
        }

        private bool FileAttachmentExists(string filename)
        {
            String prefix = chompDir(filename, 1);
            DBID tableId = getTableIdByPath(prefix);
            if (null != tableId && fileAttachmentsByTable.ContainsKey(tableId.id))
            {
                foreach (FileAttachment file in fileAttachmentsByTable[tableId.id])
                {
                    if (filename.EndsWith("\\" + file.name))
                        return true;
                }
            }
            return false;
        }

        public DirectoryInfo CreateAndGetHomeDir()
        {
            String path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\" + QB_FOLDER;
            if (!Directory.Exists(path))
                return Directory.CreateDirectory(path);
            return new DirectoryInfo(path);

        }

        private long GetFileAttachmentSize(string filename)
        {
            return api.GetFileSize(GetFileAttachmentByPath(filename));
        }

        private FileAttachment GetFileAttachmentByPath(string filename)
        {
            DBID tableId = getTableIdByPath(filename);
            if (null != tableId && fileAttachmentsByTable.ContainsKey(tableId.id))
            {
                foreach (FileAttachment file in fileAttachmentsByTable[tableId.id])
                {
                    if (filename.EndsWith("\\" + file.name))
                        return file;
                }
            }
            return null;
        }

        private FileInformation proxyDir(String name)
        {
            FileInformation fi = new FileInformation();
            fi.Attributes = FileAttributes.Directory | FileAttributes.System | FileAttributes.ReadOnly;
            fi.CreationTime = DateTime.Now;
            fi.LastAccessTime = DateTime.Now;
            fi.LastWriteTime = DateTime.Now;
            fi.Length = 0;
            fi.FileName = name;
            return fi;
		}

		private FileInformation proxyFile(String name, long size)
        {
            FileInformation fi = new FileInformation();
            //fi.Attributes = FileAttributes.ReadOnly | FileAttributes.NotContentIndexed;
            fi.Attributes = FileAttributes.Archive | FileAttributes.ReadOnly;
            fi.CreationTime = DateTime.Now;
            fi.LastAccessTime = DateTime.Now;
            fi.LastWriteTime = DateTime.Now;
            fi.Length = size;
            fi.FileName = name;
            return fi;
        }
		
        public IEnumerable GetTables(String appId)
        {
            lock (thisLock)
            {
                if (!appToTableCache.ContainsKey(appId))
                {
                    appToTableCache[appId] = new List<DBID>();
                    foreach (DBID tableId in api.GetTables(appId))
                    {
                        appToTableCache[appId].Add(tableId);
                    }
                }
                if (appToTableCache.ContainsKey(appId))
                {
                    return appToTableCache[appId];
                }
                return null;
            }
        }

        public IEnumerable GetApplications()
        {
            lock (thisLock)
            {
                TimeSpan ts = DateTime.Now - lastUpdatedApplications;
                // Every 2 minutes update
                if (applications == null || ts.Seconds > APPLICATION_UPDATE_FREQ) {
                    lastUpdatedApplications = DateTime.Now;
                    applications = api.GetApplications();
                }
                return applications;
            }
        }

        public IEnumerable GetReports(String dbid, String tablePath, DBID appId, DBID tableId)
        {
            lock (thisLock)
            {
                if (!tableToReports.ContainsKey(dbid))
                {
                    tableToReports[dbid] = new List<Report>();
                    List<QBField> fields = new List<QBField>();
                    foreach (Report report in api.GetReports(dbid, fields))
                    {
                        report.filename = tablePath + "\\" + MakeValidFileName(report.label) + ".";
                        report.appId = appId;
                        report.tableId = tableId;
                        tableToReports[dbid].Add(report);
                        if (!reportToPath.ContainsKey(report.filename))
                            reportToPath.Add(report.filename, report);
                        else
                            reportToPath[report.filename] = report;
                    }
                    if (tableFields.ContainsKey(dbid))
                        tableFields[dbid] = fields;
                    else
                        tableFields.Add(dbid, fields);
                }
                if (tableToReports.ContainsKey(dbid))
                    return tableToReports[dbid];
                return null;
            }
            
        }


        public int FindFiles(String filename, ArrayList files, DokanFileInfo info)
        {
            lock (thisLock)
            {
                if (filename.Equals("\\"))
                {
                    files.Add(proxyFile(QB_OPEN_QUICKBASE, 1024));
                    files.Add(proxyDir(QB_ROOT));
                    return 0;
                }
                else if (filename.Equals("\\" + QB_ROOT))
                {
                    foreach (DBID dbid in GetApplications())
                    {
                        files.Add(proxyDir(MakeValidFileName(dbid.label)));
                    }
                    return 0;
                }

                DBID appId = getAppIdByPath(filename); ;
                if (appId != null)
                {

                    // We're at the top level of an application
                    string appPath = "\\" + QB_ROOT + "\\" + MakeValidFileName(appId.label);
                    if (filename.Equals(appPath))
                    {
                        files.Add(proxyFile(QB_DASHBOARD, 1024));
                        int tc = 0;
                        foreach (DBID dbid in GetTables(appId.id))
                        {
                            tc++;
                            files.Add(proxyDir(MakeValidFileName(dbid.label)));
                        }
                        if (0 == tc)
                            files.Add(proxyFile(QB_CONFIGURE_APP_TOKEN, 1024));
                        return 0;
                    }
                    else
                    {
                        DBID tableId = getTableIdByPath(filename);
                        String tablePath = getTablePath(filename);

                        if (null != tableId)
                        {
                            if (filename.Equals(tablePath))
                            {
                                files.Add(proxyDir(QB_TABLE_DATA));
                                files.Add(proxyDir(QB_TABLE_REPORTS));
                                if (GetFieldsWithFileAttachment(tableId.id).Count != 0)
                                {
                                    files.Add(proxyDir(QB_FILE_ATTACHMENTS));
                                }
                                return 0;
                            }
                            else if (filename.EndsWith(QB_TABLE_REPORTS))
                            {
                                foreach (Report report in GetReports(tableId.id, tablePath, appId, tableId))
                                {
                                    files.Add(proxyFile(MakeValidFileName(report.label) + ".html", report.csvSize));
                                }
                                return 0;
                            }
                            else if (filename.EndsWith(QB_TABLE_DATA))
                            {
                                foreach (Report report in GetReports(tableId.id, tablePath, appId, tableId))
                                {
                                    // CSV data cannot be pulled from a chart, so don't list it...
                                    if (!report.type.Equals("chart"))
                                        files.Add(proxyFile(MakeValidFileName(report.label) + ".csv", report.csvSize));
                                }
                                return 0;
                            }
                            else if (filename.EndsWith(QB_FILE_ATTACHMENTS))
                            {
                                foreach (FileAttachment attachment in GetFileAttachments(tableId.id))
                                {
                                    if (!string.IsNullOrEmpty(attachment.name))
                                    {
                                        if (attachment.size == 0) {
                                            attachment.size = api.GetFileSize(attachment);
                                            if (_debug) Console.WriteLine("File: {0} size: {1}", attachment.name, attachment.size);
                                        }
                                        files.Add(proxyFile(MakeValidFileName(attachment.name), attachment.size));
                                    }
                                }
                                return 0;
                            }
                        }
                    }
                }
            }
            return -1;
        }

        private string getTablePath(string filename)
        {
            DBID dbid = getTableIdByPath(filename);
            if (null != dbid)
            {
                string label = MakeValidFileName(dbid.label);
                string path = filename.Substring(0, filename.IndexOf(label) + label.Length);
                return path;
            }
            return null;
        }

        private List<FileAttachment> GetFileAttachments(string tableId)
        {
            List<FileAttachment> files = new List<FileAttachment>();
            
            // First, does this table contain a fid with a file attachment, if yes and the answer is only one we'll support drag and drop
            List<QBField> fields = GetFieldsWithFileAttachment(tableId);
            if (fields.Count != 0)
            {
                // Generate a report that will give the list of file names, we won't pull the file until the user runs a read request on it...
                files = api.GetFileNames(tableId, fields);
            }
            if (!fileAttachmentsByTable.ContainsKey(tableId))
                fileAttachmentsByTable.Add(tableId, files);
            else
            {
                // only update those records that are new / deleted
                foreach (FileAttachment a in fileAttachmentsByTable[tableId])
                {
                    foreach (FileAttachment b in files)
                    {
                        if (a.recordId == b.recordId && a.name.Equals(b.name))
                            b.size = a.size;
                    }
                }
                fileAttachmentsByTable[tableId] = files;
            }
            return files;
        }

        private List<QBField> GetFieldsWithFileAttachment(String tableId)
        {
            List<QBField> fieldAttach = new List<QBField>();
            List<QBField> fields = new List<QBField>();
            if (!tableFields.ContainsKey(tableId))
            {
                api.GetReports(tableId, fields);
                if (!tableFields.ContainsKey(tableId))
                    tableFields.Add(tableId, fields);
                else
                    tableFields[tableId] = fields;
            }

            foreach (QBField field in tableFields[tableId])
            {
                if (field.type.Equals("file"))
                {
                    fieldAttach.Add(field);
                }
            }
            return fieldAttach;
        }

        public int SetFileAttributes(String filename, FileAttributes attr, DokanFileInfo info)
        {
            //Console.WriteLine("SetFileAttributes: {0}", filename);
            return -1;     
        }

        public int SetFileTime(String filename, DateTime ctime,
                DateTime atime, DateTime mtime, DokanFileInfo info)
        {
            return -1;
        }

        public int DeleteFile(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int DeleteDirectory(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int MoveFile(String filename, String newname, bool replace, DokanFileInfo info)
        {
            return -1;
        }

        public int SetEndOfFile(String filename, long length, DokanFileInfo info)
        {
            return -1;
        }

        public int SetAllocationSize(String filename, long length, DokanFileInfo info)
        {
            return -1;
        }

        public int LockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            return 0;
        }

        public int UnlockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            return 0;
        }

        public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes,
            ref ulong totalFreeBytes, DokanFileInfo info)
        {
            //Console.WriteLine("GetFreeSpace");
            freeBytesAvailable = 512 * 1024 * 1024;
            totalBytes = 1024 * 1024 * 1024;
            totalFreeBytes = 512 * 1024 * 1024;
            return 0;
        }

        public int Unmount(DokanFileInfo info)
        {
            return 0;
        }

        private string MakeValidFileName(string name)
        {
            string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
            string invalidReStr = string.Format(@"[{0}]+", invalidChars);
            return Regex.Replace(name, invalidReStr, "_");
        }

        static void Main(string[] args)
        {

            String domain;
            Console.WriteLine("QuickBase Q:\\ Drive - Beta 0.2\n");


            Console.Write("Domain (www.quickbase.com): ");
            domain = Console.ReadLine();

            Console.Write("Username: ");
            ConsoleColor oldFore = Console.ForegroundColor;      
            String username = Console.ReadLine();
            Console.Write("Password: ");



            Console.ForegroundColor = Console.BackgroundColor;
            String passwd = Console.ReadLine();
            Console.ForegroundColor = oldFore;

            QuickBaseClient client = new QuickBaseClient(domain, username, passwd);
            client.Authenticate();
            if (string.IsNullOrEmpty(client._ticket))
            {
                Console.WriteLine("Authentication failed.");
                System.Environment.Exit(1);
            }
            Console.Clear();
            Console.WriteLine("Authenticated as " + username + " to " + domain);
            Console.WriteLine("Open Drive Letter Q:\\");

            DokanOptions opt = new DokanOptions();
            opt.DebugMode = true;
            opt.UseStdErr = true;
            opt.VolumeLabel = "QuickBase";
            opt.MountPoint = "Q:\\";
            //opt.ThreadCount = 1;
            int status = DokanNet.DokanMain(opt, new QDrive(client));
            switch (status)
            {
                case DokanNet.DOKAN_DRIVE_LETTER_ERROR:
                    Console.WriteLine("Drvie letter error");
                    break;
                case DokanNet.DOKAN_DRIVER_INSTALL_ERROR:
                    Console.WriteLine("Driver install error");
                    break;
                case DokanNet.DOKAN_MOUNT_ERROR:
                    Console.WriteLine("Mount error");
                    break;
                case DokanNet.DOKAN_START_ERROR:
                    Console.WriteLine("Start error");
                    break;
                case DokanNet.DOKAN_ERROR:
                    Console.WriteLine("Unknown error");
                    break;
                case DokanNet.DOKAN_SUCCESS:
                    Console.WriteLine("Success");
                    break;
                default:
                    Console.WriteLine("Unknown status: %d", status);
                    break;
            }
        }
    }
}

